/*********************************************************************
 * Software License Agreement
 *
 * Copyright (C) 2010 Cross The Road Electronics.  All rights
 * reserved.
 *
 * Cross The Road Electronics (CTRE) licenses to you the right to 
 * use, copy, modify, merge, publish, distribute, sublicense, and/or 
 * sell copies of the Software ONLY when in use with CTRE's 2CAN 
 * Ethernet CAN Gateway.
 *
 * THE SOFTWARE AND DOCUMENTATION ARE PROVIDED "AS IS" WITHOUT
 * WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT
 * LIMITATION, ANY WARRANTY OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL
 * CROSS THE ROAD ELECTRONICS BE LIABLE FOR ANY INCIDENTAL, SPECIAL, 
 * INDIRECT OR CONSEQUENTIAL DAMAGES, LOST PROFITS OR LOST DATA, COST OF
 * PROCUREMENT OF SUBSTITUTE GOODS, TECHNOLOGY OR SERVICES, ANY CLAIMS
 * BY THIRD PARTIES (INCLUDING BUT NOT LIMITED TO ANY DEFENSE
 * THEREOF), ANY CLAIMS FOR INDEMNITY OR CONTRIBUTION, OR OTHER
 * SIMILAR COSTS, WHETHER ASSERTED ON THE BASIS OF CONTRACT, TORT
 * (INCLUDING NEGLIGENCE), BREACH OF WARRANTY, OR OTHERWISE.
 *
********************************************************************/
#include "UDPClient.h"

#include  "../Os Abstraction/OS_TCPIP.h"

using namespace std;

#define PRINTF(x, ...)				\
	if(m_bVerbose)					\
	{								\
		printf(x,__VA_ARGS__);		\
	}


CUDPClient::CUDPClient() :
	eState(eMark)
{
	m_RxThread = 0;
	m_InSocket = INVALID_SOCKET;
	m_OutSocket = INVALID_SOCKET;
	m_usOutRemotePort = ~0;
	m_usInRemotePort = ~0;
	m_bVerbose = true;
	m_RxCs = new OS_Semaphore();

	m_RxDataEvent = new OS_Event( TEXT("RxDataEvent"), false  );
	m_Conneced = new OS_Event( TEXT("ConnectEvent"), false  );
	
	m_StopRxThread = new OS_Event();
	m_HasStoppedRxThread  = new OS_Event(); 
	m_IsRunning  = new OS_Event(); 
}
CUDPClient::~CUDPClient()
{
	Disconnect();

	delete m_RxCs;
	m_RxCs = 0;

	delete m_RxDataEvent;
	m_RxDataEvent = 0;

	delete m_Conneced;
	m_Conneced = 0;
	
	delete m_StopRxThread;
	m_StopRxThread = 0;
	
	delete m_HasStoppedRxThread;
	m_HasStoppedRxThread = 0;

	delete m_IsRunning;
	m_IsRunning = 0;
}

void CUDPClient::CloseSockets()
{
	closesocket(m_InSocket); // this will cause recfrom to stop blocking
	m_InSocket = INVALID_SOCKET; 
	closesocket(m_OutSocket);
	m_OutSocket = INVALID_SOCKET;
}
bool CUDPClient::StartRxThread()
{
	bool reval = true;

	m_StopRxThread->Reset();
	m_HasStoppedRxThread->Reset();
	
	m_RxThread = new OS_Task(TEXT("UDPClient Thread"),RxThreadCaller,this);

	return reval;
}

void CUDPClient::StopRxThread()
{
	if(m_StopRxThread)	//Signal thread to stop
		m_StopRxThread->Signal();
	//wait for callback signal to indicate thread has finished
	bool thread_stopped = m_HasStoppedRxThread->WaitUntilSignaled(500);

	if(m_RxThread)
	{
		delete m_RxThread;
		m_RxThread = 0;
	}
}

unsigned int CUDPClient::RxThreadCaller( void * lpParam ) 
{
	CUDPClient * pThis = (CUDPClient*)lpParam;
	return pThis->RxThreadCaller();
}

unsigned int CUDPClient::RxThreadCaller() 
{
	while(1)
	{
		if(m_StopRxThread->IsSignaled() )
			break;

		Process();
	}

	m_HasStoppedRxThread->Signal();
	return 0;
}


void CUDPClient::ClearRxData()
{
	m_RxCs->Lock();
	while(!m_RxDatagrams.empty())
		m_RxDatagrams.pop();
	m_RxCs->Unlock();
	m_RxDataEvent->Reset();
}

CUDPClient::eReturnCode_t CUDPClient::Transmit(const uint8_t *txdata,unsigned int iLen,unsigned int iOptions)
{
	if(eState != eReading)
		return UDPServer_ReturnCode_NotInitialized;
	if(m_OutSocket == INVALID_SOCKET)
		return UDPServer_ReturnCode_NotInitialized;

	//ClearRxData();

	SOCKADDR_IN add;
	add.sin_family = AF_INET;
	add.sin_addr.s_addr = inet_addr( (WEAK_CONST char*)m_sRemoteIP.c_str() ); 
	add.sin_port = htons( m_usOutRemotePort );

	if (iOptions & 1) 
	{
		add.sin_addr.s_addr = inet_addr( "255.255.255.255" ); 
	}
	else
	{
		//not broadcase so IP better be set
		if(add.sin_addr.s_addr == 0)
		{
			return UDPServer_ReturnCode_RemoteIPNotSet;
		}
	}

#if 0
	{
		unsigned int optVal;
		int optLen = sizeof(optVal);
		if (getsockopt(m_OutSocket, SOL_SOCKET, SO_MAX_MSG_SIZE, (char*)&optVal, &optLen) != SOCKET_ERROR)
		{
			optLen = optLen;
		}
	}
	{
		unsigned int optVal;
		int optLen = sizeof(optVal);
		if (getsockopt(m_OutSocket, SOL_SOCKET, SO_SNDBUF, (char*)&optVal, &optLen) != SOCKET_ERROR)
		{
			optLen = optLen;
		}
	}
	{
		DWORD  optVal = 1;
		int optLen = sizeof(optVal);
		//if (setsockopt(m_OutSocket, IPPROTO_IP, IP_DONTFRAGMENT, (char*)&optVal, optLen) != SOCKET_ERROR)
		{
			optLen = optLen;
		}
	}
	{
		DWORD  optVal;
		int optLen = sizeof(optVal);
		if (getsockopt(m_OutSocket, IPPROTO_IP, IP_DONTFRAGMENT, (char*)&optVal, &optLen) != SOCKET_ERROR)
		{
			optLen = optLen;
		}
	}
#endif




	int br = sendto(m_OutSocket,(WEAK_CONST char*)txdata,iLen,0,(WEAK_CONST sockaddr*)&add,sizeof(add));
	if(br == SOCKET_ERROR)
	{
		int le = WSAGetLastError();
		PRINTF("Could not open socket with error: %d\n", le);
		return UDPServer_ReturnCode_TxFailed;
	}		
	return UDPServer_ReturnCode_Ok;
}

int CUDPClient::PollRxSocket(int timeoutMs)
{		
#ifdef OS_ABSTRACTION_VXWORKS
	return 2;
#else
	fd_set sock_set;
	sock_set.fd_array[0] = m_InSocket;
	sock_set.fd_count = 1;
	timeval timeout = {0};
	timeout.tv_usec = timeoutMs * 1000;
	int ret_select = select(0,&sock_set,0,0,&timeout);
	//int ret_select = select(0,&sock_set,0,0,NULL);
	if(ret_select == SOCKET_ERROR)
	{
		// error
		int le = WSAGetLastError();
		PRINTF("SOCKET_ERROR on select with error: %d\n", le);
		eState = eFail;
		return 0;
	}
	else if(ret_select == 0)
	{
		//time expired
		return 1;
	}
	else
	{
		//one or more socket is readable
		return 2;
	}
	return 0;
#endif
}
void CUDPClient::Process()
{
	switch(eState)
	{
		case eMark:
			Sleep(10);
			break;

		case eInitializing:
		{
#ifdef OS_ABSTRACTION_VISUAL_STUDIO
			uint16_t wVersionRequested = MAKEWORD(2, 2);
			WSADATA wsaData = {0};

			unsigned long err;
			err= WSAStartup(wVersionRequested, &wsaData);
			if (err != 0) 
			{
				PRINTF("WSAStartup failed with error: %d\n", err);
				eState = eFail;
				return;
			}
#endif
			
			m_InSocket = socket(  AF_INET ,  SOCK_DGRAM,IPPROTO_UDP );
			m_OutSocket = socket(  AF_INET ,  SOCK_DGRAM,IPPROTO_UDP );

			if(		(m_InSocket == INVALID_SOCKET)  ||
					(m_OutSocket == INVALID_SOCKET) ||
					0)
			{
				int le = WSAGetLastError();
				printf("Could not open socket with error: %d\n", le);
				eState = eFail;
				return;
			}
			
			eState = eInitialized;
		}	break;

		case eInitialized:
		{
			struct sockaddr_in	sin = {0};

			sin.sin_family = AF_INET;
			sin.sin_port = htons(m_usInRemotePort);
			sin.sin_addr.s_addr = INADDR_ANY;

			if (bind(m_InSocket, (struct sockaddr*)&sin, sizeof(sin)) < 0)
			{
				int le = WSAGetLastError();
				PRINTF("Bind failed with error: %d\n", le);
				eState = eFail;
				return;
			}

			BOOL bOptVal = 1;
			int bOptLen = sizeof(BOOL);
			if (setsockopt(m_OutSocket,SOL_SOCKET,SO_BROADCAST,(char*)&bOptVal, bOptLen) == SOCKET_ERROR) 
			{
				int le = WSAGetLastError();
				PRINTF("setsockopt failed with error: %d\n", le);
				eState = eFail;
				return;
			}

			eState = eReading;
			m_Conneced->Signal();
		}	break;

		case eReading:
		{  

			sockaddr_in SenderAddr;
			int sin_len = sizeof(SenderAddr);
			char data[2048];


			int i = PollRxSocket(50);
			if(i == 0)
			{
				// error
				int le = WSAGetLastError();
				PRINTF("SOCKET_ERROR on select with error: %d\n", le);
				eState = eFail;
				return;
			}
			else if(i == 2)
			{
				int iNumBytes = recvfrom(m_InSocket,
										 (char*)data,
										 sizeof(data),
										 0,
										 (SOCKADDR *)&SenderAddr,
										 &sin_len);

				if(iNumBytes == SOCKET_ERROR)
				{
					int le = WSAGetLastError();
					PRINTF("SOCKET_ERROR on read with error: %d\n", le);
					eState = eFail;
					return;
				}
				else if(iNumBytes>0)
				{
					m_RxCs->Lock();
					RxDatagram rxDatagram;
					rxDatagram.Len = iNumBytes;
					memcpy(rxDatagram.Data,data,rxDatagram.Len);
					
#ifdef OS_ABSTRACTION_VISUAL_STUDIO
					rxDatagram.Ip = SenderAddr.sin_addr.S_un.S_addr;
#elif defined(OS_ABSTRACTION_VXWORKS)
					rxDatagram.Ip = SenderAddr.sin_addr.s_addr;
#else
					#error Don't know what to do!
#endif
					m_RxDatagrams.push(rxDatagram);
					m_RxCs->Unlock();
					
					m_RxDataEvent->Signal();
				}
			}
			
		}	break;

		case eFail:
		{
			closesocket(m_InSocket);
			closesocket(m_OutSocket);

			m_InSocket = 0;
			m_OutSocket = 0;

			//eState = eMark;
			eState = eInitializing;
		}	break;
	}
}


CUDPClient & CUDPClient::SetOutRemotePort(unsigned short usRemotePort)
{
	m_usOutRemotePort = usRemotePort;
	return *this;
}
CUDPClient & CUDPClient::SetInLocalPort(unsigned short usRemotePort)
{
	m_usInRemotePort = usRemotePort;
	return *this;
}
CUDPClient & CUDPClient::SetRemoteIP(const std::string & sRemoteIP)
{
	m_sRemoteIP = sRemoteIP;
	return *this;
}
CUDPClient & CUDPClient::Connect()
{
	if(m_IsRunning->IsSignaled())
		Disconnect();

	eState = eInitializing;
	StartRxThread();

	m_IsRunning->Signal();

	return *this;
}
CUDPClient & CUDPClient::Disconnect()
{
	if(m_IsRunning->IsSignaled())
	{
		CloseSockets();
		StopRxThread();
		
		eState = eInitializing;
		
		m_IsRunning->Reset();
	}
	return *this;
}
std::string CUDPClient::ToString(const CUDPClient::eReturnCode_t & er)
{
	std::string s = "unknown error";

	switch(er)
	{		
		case UDPServer_ReturnCode_Ok:
			s = "UDPServer_ReturnCode_Ok";
			break;
		case UDPServer_ReturnCode_NotInitialized:
			s = "UDPServer_ReturnCode_NotInitialized";
			break;
		case UDPServer_ReturnCode_TxFailed:
			s = "UDPServer_ReturnCode_TxFailed";
			break;
		case UDPServer_ReturnCode_RemoteIPNotSet:
			s = "UDPServer_ReturnCode_RemoteIPNotSet";
			break;
	}

	return s;
}

unsigned long CUDPClient::Read(unsigned char * pData,unsigned long ulCapacity,uint32_t &ulIP)
{
	unsigned long ulSize = ulCapacity;

	m_RxCs->Lock();
	
	if(m_RxDatagrams.empty()==false)
	{
		RxDatagram & datagram = m_RxDatagrams.front();

		if(ulSize > datagram.Len)
			ulSize = datagram.Len;

		memcpy(pData,datagram.Data,ulSize);
		
		ulIP = datagram.Ip;

		m_RxDatagrams.pop();
	}
	else
		ulSize = 0;

	m_RxCs->Unlock();

	return ulSize;
}
bool CUDPClient::HasData()
{
	bool bRetVal = false;

	m_RxCs->Lock();

	//if(m_RxDatagramLen)
	//	bRetVal = true;;
	bRetVal = !m_RxDatagrams.empty();

	m_RxCs->Unlock();

	return bRetVal;
}


bool CUDPClient::WaitForRxData(unsigned long iTimeoutMs)
{
	if(HasData())
		return true;

	if(m_RxDataEvent->WaitUntilSignaled(iTimeoutMs))
		return true;

	return true;
}

bool CUDPClient::WaitUntilConnected(uint32_t timeout)
{
	if(m_Conneced->WaitUntilSignaled(timeout))
		return true;

	return false;
}
